#include "dicom/web_server.h"

#include <onion/log.h>
#include <stdarg.h>
#include <gwbase/base/logging.h>
#include <gwbase/base/util.h>
#include <gwbase/base/string_util.h>
#include "dicom/handler/handler_common.h"
namespace dicom {

static LogLevel OnionLevelToLogLevel(onion_log_level level) {
  switch (level) {
    case O_DEBUG0:
    case O_DEBUG:
      return kLogDebug;
    case O_INFO:
      return kLogInfo;
    case O_WARNING:
      return kLogWarn;
    case O_ERROR:
      return kLogError;
    default:
      return kLogDebug;
  }
}

void OnionLogWrapper(onion_log_level level, const char* filename, int lineno,
                     const char* fmt, ...) {
  if (dicom::GetLogLevel() > OnionLevelToLogLevel(level)) return;

  SourceLocation location(filename, lineno);
  std::string result;
  va_list ap;
  va_start(ap, fmt);
  StringPrintfV(&result, fmt, ap);
  va_end(ap);

  dicom::LogEntry(dicom::OnionLevelToLogLevel(level), location) << "ONION "
                                                                << result;
}

static onion_connection_status HandlerWrapper(void* d, onion_request* req,
                                              onion_response* resp) {
  WebServer* w = static_cast<WebServer*>(d);
  int result = w->HandleRequest(req, resp);
  LOG_TRACE << "result: " << result;
  return OCS_PROCESSED;
}

WebServer::WebServer(Gateway* gw, const DicomConf* conf)
    : gw_(gw), conf_(conf) {}

void WebServer::Start() {
  onion_log = OnionLogWrapper;
  onion* o = onion_new(O_THREADED);
  // See https://github.com/davidmoreno/onion/issues/116
  // We use INT_MAX to disable timeout
  ::onion_set_timeout(o, INT_MAX);
  assert(gw_->put_sink_pool() != NULL);
  ::onion_set_put_sink_pool(o, gw_->put_sink_pool()->OnionPool());
  onion_handler* root = onion_handler_new(HandlerWrapper, this, NULL);
  ::onion_set_root_handler(o, root);
  ::onion_set_max_file_size(o, 1024UL * 1024 * 1024 * 100);
  int port = conf_->GetListenPort();
  if (port <= 0 || port >= 65535) port = 3322;
  std::string port_s;
  StringPrintf(&port_s, "%d", port);
  ::onion_set_port(o, port_s.c_str());
  ::onion_listen(o);
}

void WebServer::DestroyHandler(onion_request* req) {
  /*
  HttpHandler* t = static_cast<HttpHandler*>(NULL);
  t->Cleanup(NULL);
  t->Unref();
  */
}

int WebServer::HandleRequest(onion_request* req, onion_response* resp) {
  UrlParams params;
  Location* loc = MatchLocation(req, &params);
  if (loc == NULL) {
    LOG_WARN << "can't get handler for uri: " << onion_request_get_path(req);
    SendSimpleResponse(resp, 404, "no handler");
    return OCS_PROCESSED;
  }

  HttpHandler* h = loc->handler_cb(req);
  h->AddRef();
  h->HandleRequest(req, resp);
  h->Cleanup();
  h->Unref();
  return OCS_PROCESSED;
}

void WebServer::AddHandler(const std::string& url, const std::string& methods,
                           const CreateHandlerCallback& cb) {
  const std::string pat = "^" + url + "$";
  locs_.push_back(new Location(pat, methods, cb));
}

WebServer::Location* WebServer::MatchLocation(onion_request* req,
                                              UrlParams* params) {
  params->clear();
  const char* path = ::onion_request_get_path(req);
  const char* method =
      ::onion_request_methods[(::onion_request_get_flags(req) & 0x0F)];

  std::string path2 = std::string("/") + path;

  for (size_t i = 0; i < locs_.size(); ++i) {
    Location* loc = locs_[i];
    if (!loc->pattern.MatchUrl(path2.c_str(), params)) continue;

    if (loc->methods != "*" &&
        loc->methods.find(method, 0) == std::string::npos)
      continue;

    LOG_TRACE << "MATCHED " << method << ": " << path2;
    return loc;
  }

  return NULL;
}
}
